#include <limits>

#include <QSqlQuery>
#include <QSqlError>
#include <QApplication>

#include <src/ApplicationEngine.h>

#include "../../global/QmlNames.h"
#include "../../persist/DbManager.h"
#include "../../config/Config.h"
#include "../LocalUserModel.h"
#include "../GroupsModel.h"
#include "../RosterModel.h"
#include "SessionModel.h"

X_BEGIN_NAMESPACE

// Private data
SessionModelPrivate::SessionModelPrivate()
    : QSharedData()
{
    m_pinToTop = false;
    m_showInList = true;

    m_actualChatObject = NULL;
}

const char SessionModel::READ_MESSAGES[] = "select id, content, content_type, sender_barejid, time, thumbnail from messages m where m.session_id = :session_id and id < :start_id order by id desc limit :count";
const char SessionModel::INSERT_MESSAGE[] = "insert into messages(id, session_id, content, content_type, sender_barejid, time) values(:id, :session_id, :content, :content_type, :sender_barejid, :time)";

/**
 * @brief 会话模型。表示单个会话。
 *
 * 一个会话可能是P2P，群聊或临时讨论组类型。具体的对象属性需要根据类型判断。
 */
SessionModel::SessionModel()
    : QObject(NULL)
    , d(new SessionModelPrivate)
{
}

SessionModel::SessionModel(const QString &id)
    : QObject(NULL)
    , d(new SessionModelPrivate)
{
    d->m_id = id;
}

SessionModel::SessionModel(const SessionModel &src)
    : d(src.d)
{

}

SessionModel &SessionModel::operator =(const SessionModel &rhs)
{
    if(this != &rhs) this->d = rhs.d;

    return *this;
}

const char *SessionModel::qmlName()
{
    return QmlNames::SubModelType::SessionModel;
}

/**
 * @brief 按会话ID获取消息，支持分页。
 *
 * 消息的本地ID是一个递增的整数，因此在搜索历史消息的时候，需要使用小于比较并使用降序检索，这样才能得到正常顺序的历史消息。
 * 但视图上的消息是以升序显示的，因此模型返回的结果是升序的，即对模型做反转。
 *
 * 当消息大于指定的数量时，需要使用消息管理器查看。
 *
 * @param startId
 * @param count
 * @return
 */
QVariant SessionModel::loadMessages(int startId, int count)
{
    // startId为-1时表示加载最新的消息，适用于第一次消息加载，之后需要传递具体的消息ID
    startId = startId == -1 ? std::numeric_limits<int>::max() : startId;

    // 消息列表不为空的情况下，只加载ID有差异的部分,且最多加载 MAX_MESSAGE_COUNT 条消息，更多历史消息参见消息管理器
    QList<MessageModel>& messages = d->m_messageList.d->m_messages;
    if(!messages.isEmpty() && messages.last().id() < startId){
        startId = messages.last().id();
        if(count + messages.size() > MAX_MESSAGE_COUNT) count = MAX_MESSAGE_COUNT - messages.size();
    }

    // 加载消息
    QSqlQuery query = DbManager::instance().createQuery();
    query.prepare(READ_MESSAGES);
    query.bindValue(":session_id", d->m_id);
    query.bindValue(":start_id", startId);
    query.bindValue(":count", count);
    query.exec();

    while (query.next()) {
        // id, content, content_type, sender_barejid, time, thumbnail
        int messageId = query.value("id").toInt();
        QString content = query.value("content").toString();
        MessageContentType contentType = MessageContentType(query.value("content_type").toInt());
        QString senderJid = query.value("sender_barejid").toString();
        QDateTime time = query.value("time").toDateTime();
        QString thumbnail = query.value("thumbnail").toString();

        // 注意cow造成的影响，如果消息先构建，再通过函数返回，之后再设置消息的属性，
        // cow会修改新的数据指针，而不是原始的数据，造成数据丢失
        addMessage(messageId, senderJid, content, thumbnail, contentType, time, false);
    }

    // 反转遍历消息列表，并返回给视图
    QVariantList list;
    QList<MessageModel>::reverse_iterator rit = messages.rbegin();
    while (rit != messages.rend()) {
        list.append(QVariant::fromValue(&(*rit)));

        ++rit;
    }

    return list;
}

/**
 * @brief 发送消息。
 *
 * 消息会追加到列表的前面，因为是按降序排列的
 *
 * @param messageConent
 * @param messageConentType
 */
void SessionModel::sendMessage(const QString &messageConent, MessageContentType messageConentType)
{
    // 构建消息
    MessageModel message = addMessage(0,
                                      LocalUser.bareJid(),
                                      messageConent,
                                      "",
                                      messageConentType,
                                      QDateTime::currentDateTime(),
                                      true);

    // 保存
    QSqlQuery query = DbManager::instance().createQuery();
    query.prepare(INSERT_MESSAGE);
    query.bindValue(":session_id", d->m_id);
    query.bindValue(":id", message.id());
    query.bindValue(":content", message.content());
    query.bindValue(":content_type", (int)message.contentType());
    query.bindValue(":sender_barejid", message.senderJid());
    query.bindValue(":time", message.time());

    if(!query.exec()){
        qDebug() << "Save message to database failed: " << query.lastError().text();
    }

    emit lastMessageTimeChanged(d->m_id, QDateTime::currentDateTime());

    // 发送
    ApplicationEngine *engine = qApp->property("engine").value<ApplicationEngine *>();
    if(d->m_sessionType == SessionType::P2P){
        engine->xmppClient().sendMessage(d->m_id, messageConent);
    }
}

/**
 * @brief 接收消息。
 *
 * @param senderJid
 * @param content
 * @param MessageContentType
 */
void SessionModel::receivedMessage(const QString &senderJid, const QString &content, MessageContentType contentType, QDateTime stamp)
{
    MessageModel message = addMessage(0, senderJid, content, "", contentType, stamp, true);

    // 保存
    QSqlQuery query = DbManager::instance().createQuery();
    query.prepare(INSERT_MESSAGE);
    query.bindValue(":session_id", d->m_id);
    query.bindValue(":id", message.id());
    query.bindValue(":content", message.content());
    query.bindValue(":content_type", (int)message.contentType());
    query.bindValue(":sender_barejid", message.senderJid());
    query.bindValue(":time", message.time());

    if(!query.exec()){
        qDebug() << "Save message to database failed: " << query.lastError().text();
    }

    emit lastMessageTimeChanged(d->m_id, QDateTime::currentDateTime());
}

/**
 * @brief 置顶会话
 * @return
 */
bool SessionModel::pinToTop() const
{
    return d->m_pinToTop;
}

/**
 * @brief 是否在列表中显示会话
 * @return
 */
bool SessionModel::showInList() const
{
    return d->m_showInList;
}

/**
 * @brief 会话ID
 * @return
 */
QString SessionModel::id() const
{
    return d->m_id;
}

const MessageModel* SessionModel::lastMessage() const
{
    if(d->m_messageList.d->m_messages.isEmpty()) return NULL;

    return &(d->m_messageList.d->m_messages.first());
}

/**
 * @brief 返回实际的会话联系人。
 * @return
 */
ContactModel *SessionModel::contact()
{
    if(d->m_sessionType != SessionType::P2P) return NULL;
    if(d->m_actualChatObject == NULL){
        d->m_actualChatObject = RosterModel::instance().findContact(d->m_id);
    }

    return (ContactModel *)d->m_actualChatObject;
}

/**
 * @brief 返回实际的的会话群组。
 * @return
 */
GroupModel *SessionModel::group()
{
    if(d->m_sessionType != SessionType::Group) return NULL;
    if(d->m_actualChatObject == NULL){
        d->m_actualChatObject = GroupsModel::instance().findGroup(d->m_id);
    }

    return (GroupModel *)d->m_actualChatObject;
}

/**
 * @brief 会话类型。
 * @return
 */
SessionType SessionModel::sessionType() const
{
    return d->m_sessionType;
}

/**
 * @brief 名称。显示优先级：本地备注姓名>昵称>bareJid
 *
 * @return
 */
QString SessionModel::name()
{
    ContactModel* contactModel = contact();
    if(contactModel){
        if(contactModel->remarkName().size() > 0) return contactModel->remarkName();
        if(contactModel->nickName().size() > 0) return contactModel->nickName();

        return contactModel->bareJid();
    }

    GroupModel* groupModel = group();
    if(groupModel){
        return groupModel->name();
    }

    return "Invalid Session Name";
}

/**
 * @brief 照片。
 * @return
 */
QString SessionModel::photo()
{
    ContactModel* contactModel = contact();
    if(contactModel){
        return contactModel->thumbnail();
    }

    GroupModel* groupModel = group();
    if(groupModel){
        return groupModel->photo();
    }

    return Config::instance().defaultAvatar();
}

/**
 * @brief 最后消息内容。
 * @return
 */
QString SessionModel::lastMessageContent() const
{
    const MessageModel* message = lastMessage();
    return message ? message->content() : "";
}

/**
 * @brief 最后消息内容类型。
 * @return
 */
MessageContentType SessionModel::lastMessageContentType() const
{
    const MessageModel* message = lastMessage();
    return message ? message->contentType() : MessageContentType::None;
}

/**
 * @brief 最后消息发送人。
 * @return
 */
QString SessionModel::lastSenderName() const
{
    const MessageModel* message = lastMessage();
    return message ? message->senderName() : "";
}

/**
 * @brief 最后消息发送人JID。
 * @return
 */
QString SessionModel::lastSenderJid() const
{
    const MessageModel* message = lastMessage();
    return message ? message->senderJid() : "";
}

/**
 * @brief 最后消息时间。
 * @return
 */
QDateTime SessionModel::lastMessageTime() const
{
    const MessageModel* message = lastMessage();
    return message ? message->time() : QDateTime();
}

SessionModel& SessionModel::setSessionType(SessionType sessionType)
{
    if (d->m_sessionType == sessionType)
        return *this;

    d->m_sessionType = sessionType;
    emit sessionTypeChanged(sessionType);

    return *this;
}

SessionModel& SessionModel::setPinToTop(bool pinToTop)
{
    if (d->m_pinToTop == pinToTop)
        return *this;

    d->m_pinToTop = pinToTop;
    emit pinToTopChanged(pinToTop);

    return *this;
}

SessionModel& SessionModel::setShowInList(bool showInList)
{
    if (d->m_showInList == showInList)
        return *this;

    d->m_showInList = showInList;
    emit showInListChanged(showInList);

    return *this;
}

/**
 * @brief 增加一条消息
 * @param messageId
 * @param senderJid
 * @param senderName
 * @param senderPhoto
 * @param content
 * @param contentType
 * @param time
 */
MessageModel SessionModel::addMessage(int messageId,
                                      const QString &senderJid,
                                      const QString &content,
                                      const QString &thumbnail,
                                      MessageContentType contentType,
                                      QDateTime time,
                                      bool appendAtFront)
{
    bool self = senderJid == LocalUser.bareJid();
    bool p2pSession = d->m_sessionType == SessionType::P2P;

    return d->m_messageList.insertMessage(messageId,
                                          senderJid,
                                          "",
                                          self ? LocalUser.photo() : (p2pSession ? contact()->photo() : "qrc:/images/avatar_default.png"),
                                          content,
                                          thumbnail,
                                          contentType,
                                          time,
                                          appendAtFront);
}

X_END_NAMESPACE
